/* Copyright (c) 2004-2024 Peigen.info. All rights reserved. */
package info.peigen.hotpot.component.doc.core.publish;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.BorderStyle;
import com.deepoove.poi.data.style.Style;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import info.peigen.hotpot.common.core.enums.YesOrNo;
import info.peigen.hotpot.common.core.lang.money.Money;
import info.peigen.hotpot.common.core.result.ResultCode;
import info.peigen.hotpot.common.core.util.EnumUtil;
import info.peigen.hotpot.component.doc.core.config.HotpotDocProperties;
import info.peigen.hotpot.component.doc.core.domain.HotpotApi;
import info.peigen.hotpot.component.doc.core.domain.HotpotDoc;
import info.peigen.hotpot.component.doc.core.domain.HotpotDocAttribute;
import info.peigen.hotpot.component.doc.core.resolver.HotpotDocAttributeResolver;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import static info.peigen.hotpot.common.core.enums.CommentType.*;

/**
 * <b>(HotpotDocPublish)</b>
 *
 * @author Peigen
 * @version 1.0.0
 * @since 2023/12/8
 */
@Component
public class HotpotDocPublish {
    public static HotpotDoc      doc       = HotpotDoc.builder().apis(Lists.newArrayList()).build();
    private final Set<HotpotApi> appendixs = Sets.newLinkedHashSet();

    @Inject
    HotpotDocProperties properties;

    public void publish() {
        publish(doc);
    }

    public void publish(HotpotDoc doc) {
        if (!properties.isEnabled()) {
            return;
        }
        String outputPath = properties.getOutputPath();
        doc.setTitle(properties.getTitle());

        XWPFTemplate template = XWPFTemplate.create(createDocumentRenderData(doc));

        try {
            template.writeAndClose(new FileOutputStream(outputPath));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private DocumentRenderData createDocumentRenderData(HotpotDoc doc) {
        DocumentRenderData document = Documents.of().create();
        // 1. Title
        ParagraphRenderData title = Paragraphs.of(Texts.of(doc.getTitle()).style(titleStyle()).create()).center()
                .create();
        document.addParagraph(title);

        // 2. Public
        genPublic(document);

        // 2. ApiTable
        List<HotpotDocPublishObject> publishObjects = transfer(doc.getApis());
        publishObjects.forEach(obj -> {
            // 2.1 ApiName
            String text = "接口名：" + obj.getApiName();
            document.addParagraph(Paragraphs.of(Texts.of(text).style(paragraphStyle()).create())
                    .borderLeft(paragraphBorderStyle()).create());

            // 2.2 ApiCode
            text = "接口码：" + obj.getApiCode();
            document.addParagraph(Paragraphs.of(Texts.of(text).style(paragraphStyle()).create())
                    .borderLeft(paragraphBorderStyle()).create());

            // 2.3 ApiAttributes
            // 2.3.1 RequestAttributes
            text = "入参：";
            if (CollUtil.isEmpty(obj.getRequestTable())) {
                text = "入参：无";
            }
            document.addParagraph(Paragraphs.of(Texts.of(text).style(attributeStyle()).create())
                    .borderLeft(attributeBorderStyle()).create());
            int[] width = {15, 12, 9, 9, 12, 43};
            if (CollUtil.isNotEmpty(obj.getRequestTable())) {
                TableRenderData requestTable = Tables.of(obj.getRequestTable().toArray(RowRenderData[]::new))
                        .percentWidth("100%", width).create();
                document.addTable(requestTable);
            }

            // 2.3.2 ResponseAttributes
            text = "出参：";
            if (CollUtil.isEmpty(obj.getResponseTable())) {
                text = "出参：无";
            }
            document.addParagraph(Paragraphs.of(Texts.of(text).style(attributeStyle()).create())
                    .borderLeft(attributeBorderStyle()).create());
            TableRenderData responseTable = Tables.of(obj.getResponseTable().toArray(RowRenderData[]::new))
                    .percentWidth("100%", width).create();
            document.addTable(responseTable);
        });

        // 3. appendixTable
        String text = "附录";
        addBlankLine(document);
        document.addParagraph(Paragraphs.of(Texts.of(text).style(attributeStyle()).create())
                .borderLeft(attributeBorderStyle()).create());

        List<HotpotApi>              newAppendixs    = appendixs.stream().toList();
        List<HotpotDocPublishObject> appendixObjects = transfer(newAppendixs);
        appendixObjects.forEach(app -> {
            addBlankLine(document);
            document.addParagraph(Paragraphs.of(Texts.of("对象名：" + app.getApiName()).style(paragraphStyle()).create())
                    .borderLeft(paragraphBorderStyle()).create());
            TableRenderData appendixTable = Tables.create(app.getResponseTable().toArray(new RowRenderData[0]));
            document.addTable(appendixTable);
        });

        genErrorCode(document);

        return document;
    }

    private List<HotpotDocPublishObject> transfer(List<HotpotApi> apis) {
        return apis.stream().map(api -> HotpotDocPublishObject.builder().apiName(api.getName()).apiCode(api.getCode())
                .requestTable(resolverTable(api.getInAttributes()))
                .responseTable(resolverTable(api.getOutAttributes()))
                .build()).toList();
    }

    private List<RowRenderData> resolverTable(List<HotpotDocAttribute> attributes) {
        if (CollUtil.isEmpty(attributes)) {
            return null;
        }
        // 1. 表头
        RowRenderData header = Rows.of("属性名", "类型", "长度", "必填", "默认值", "描述").textFontSize(12)
                .textColor("000000").bgColor("DCDCDC").textBold().center().create();

        // 2. 分级解析主文档和附录
        List<RowRenderData> rows = Lists.newArrayList(header);
        rows.addAll(attributes.stream().filter(ObjUtil::isNotEmpty).map(att -> {
            String description = (String) att.getDescription();

            if (EnumUtil.anyMatch(att.getType(), Class, Json, List) &&
                    notEquals(att.getDescriptionClass(), Money.class, java.lang.Enum.class, java.lang.String.class)) {
                resolverAppendix(att);
            }

            CellRenderData nameCell    = Cells.create(att.getName());
            CellRenderData typeCell    = parseTypeCell(att.getType().message());
            CellRenderData sizeCell    = Cells.create(att.getSize());
            CellRenderData fillCell    = Cells.create(YesOrNo.whether(att.getRequiredFields()));
            CellRenderData defaultCell = Cells.create(att.getDefaultValue());
            CellRenderData descCell    = Cells.create(description);

            // return Rows.of(att.getName(), att.getType().message(), YesOrNo.whether(att.getRequiredFields()), att.getDefaultValue(), description).textFontSize(10).center().create();
            return Rows.of(nameCell, typeCell, sizeCell, fillCell, defaultCell, descCell).textFontSize(10).center()
                    .create();
        }).toList());
        return rows;
    }

    private boolean notEquals(Object obj1, Object... obj2) {
        return Arrays.stream(obj2).allMatch(o -> ObjUtil.notEqual(obj1, o));
    }

    private CellRenderData parseTypeCell(String type) {
        String color = "";
        switch (type) {
            // 红色
            case "JSON" -> color = "FF0000";
            // 绿色
            case "集合" -> color = "008000";
            // 橙色
            case "枚举" -> color = "FFA500";
        }

        return Cells.of(Texts.of(type).color(color).create()).create();
    }

    private void resolverAppendix(HotpotDocAttribute att) {
        Class<?> cls = att.getDescriptionClass();
        HotpotApi api = HotpotApi.builder().name(cls.getSimpleName())
                .outAttributes(HotpotDocAttributeResolver.resolver(cls)).build();
        if (appendixs.stream().noneMatch(app -> StrUtil.equals(app.getName(), api.getName()))) {
            appendixs.add(api);
        }
        api.getOutAttributes().stream()
                .filter(ObjUtil::isNotNull)
                .filter(attribute -> EnumUtil.anyMatch(attribute.getType(), Class, Json, List))
                .filter(attribute -> attribute.getType() == List && !attribute.getDescriptionClass()
                        .isEnum() && !ObjUtil.equal(attribute.getDescriptionClass(), java.lang.String.class))
                .forEach(this::resolverAppendix);
    }

    /**
     * 生成公共参数表
     *
     * @param document DocumentRenderData
     */
    private void genPublic(DocumentRenderData document) {
        String text = "公共参数：";
        document.addParagraph(Paragraphs.of(Texts.of(text).style(paragraphStyle()).create())
                .borderLeft(paragraphBorderStyle()).create());

        text = "请求地址：";
        document.addParagraph(Paragraphs.of(Texts.of(text).style(paragraphStyle()).create())
                .borderLeft(paragraphBorderStyle()).create());
        int[] width = {16, 42, 42};
        // 请求地址表头
        RowRenderData header = Rows.of("环境", "HTTP地址", "HTTPS地址").textFontSize(12).textColor("000000")
                .bgColor("DCDCDC").textBold().center().create();
        List<RowRenderData> rows = Lists.newArrayList(header);
        rows.add(Rows.of("开发环境", properties.getPublicDevHttp(), properties.getPublicDevHttps()).textFontSize(10)
                .center().create());
        rows.add(Rows.of("测试环境", properties.getPublicTestHttp(), properties.getPublicTestHttps()).textFontSize(10)
                .center().create());
        rows.add(Rows.of("生产环境", properties.getPublicProdHttp(), properties.getPublicProdHttps()).textFontSize(10)
                .center().create());

        TableRenderData requestTable = Tables.of(rows.toArray(RowRenderData[]::new)).percentWidth("100%", width)
                .create();
        document.addTable(requestTable);

        text = "公共请求参数：";
        document.addParagraph(Paragraphs.of(Texts.of(text).style(paragraphStyle()).create())
                .borderLeft(paragraphBorderStyle()).create());
        width = new int[]{15, 12, 9, 9, 12, 43};
        // 公共请求参数表头
        header = Rows.of("属性名", "类型", "长度", "必填", "默认值", "描述").textFontSize(12).textColor("000000")
                .bgColor("DCDCDC").textBold().center().create();
        rows = Lists.newArrayList(header);
        rows.add(Rows
                .of("requestNo", "字符串", "30", "是", "", "请求号，不得重复；格式：yyyyMMddHHmmss+abbreviation(8)+随机数，abbreviation为合作伙伴专有编码（八位）")
                .textFontSize(10).center().create());
        rows.add(Rows.of("apiCode", "字符串", "300", "是", "", "接口码").textFontSize(10).center().create());
        rows.add(Rows.of("partnerId", "字符串", "30", "是", "", "合作伙伴Id").textFontSize(10).center().create());
        rows.add(Rows.of("encrypt", "字符串", "", "是", "", "加密串").textFontSize(10).center().create());
        rows.add(Rows.of("userIp", "字符串", "20", "否", "", "客户浏览器 IP").textFontSize(10).center().create());
        rows.add(Rows.of("version", "字符串", "20", "否", "1.0", "接口版本").textFontSize(10).center().create());
        rows.add(Rows.of("sign", "字符串", "100", "否", "", "签名").textFontSize(10).center().create());

        requestTable = Tables.of(rows.toArray(RowRenderData[]::new)).percentWidth("100%", width).create();
        document.addTable(requestTable);

        text = "公共响应参数：";
        document.addParagraph(Paragraphs.of(Texts.of(text).style(paragraphStyle()).create())
                .borderLeft(paragraphBorderStyle()).create());
        width = new int[]{12, 12, 9, 15, 52};
        // 公共请求参数表头
        header = Rows.of("属性名", "类型", "必填", "默认值", "描述").textFontSize(12).textColor("000000")
                .bgColor("DCDCDC").textBold().center().create();
        rows = Lists.newArrayList(header);
        rows.add(Rows.of("status", "枚举", "是", "initialization", """
                本次调用是否成功；
                initialization:初始化
                success:成功
                failure:失败
                processing:处理中
                """).textFontSize(10).center().create());
        rows.add(Rows.of("code", "字符串", "是", "", "错误码，参看附录：错误码").textFontSize(10).center().create());
        rows.add(Rows.of("detail", "字符串", "是", "", "错误原因的中文描述").textFontSize(10).center().create());
        rows.add(Rows.of("info", "对象", "否", "", "返回对象：如果有返回对象的话则在此封装").textFontSize(10).center()
                .create());
        requestTable = Tables.of(rows.toArray(RowRenderData[]::new)).percentWidth("100%", width).create();
        document.addTable(requestTable);
    }

    /**
     * 生成错误码表
     *
     * @param document DocumentRenderData
     */
    public void genErrorCode(DocumentRenderData document) {
        String text = "错误码：";
        document.addParagraph(Paragraphs.of(Texts.of(text).style(paragraphStyle()).create())
                .borderLeft(paragraphBorderStyle()).create());

        int[] width = {50, 50};
        // 请求地址表头
        RowRenderData header = Rows.of("错误码", "说明").textFontSize(12).textColor("000000").bgColor("DCDCDC")
                .textBold().center().create();
        List<RowRenderData> rows = Lists.newArrayList(header);
        List<RowRenderData> errorCodeRows = Arrays.stream(ResultCode.values()).map(code ->
                Rows.of(code.code(), code.message()).textFontSize(10).center().create()
        ).toList();
        rows.addAll(errorCodeRows);

        TableRenderData requestTable = Tables.of(rows.toArray(RowRenderData[]::new)).percentWidth("100%", width)
                .create();
        document.addTable(requestTable);
    }

    // 标题
    private Style titleStyle() {
        return Style.builder().buildColor("000000").buildFontSize(13).buildBold().build();
    }

    // 段落
    private Style paragraphStyle() {
        return Style.builder().buildColor("000000").buildFontSize(12).buildBold().build();
    }

    // 属性
    private Style attributeStyle() {
        return Style.builder().buildColor("000000").buildFontSize(12).buildBold().build();
    }

    private BorderStyle paragraphBorderStyle() {
        return BorderStyle.builder().withType(XWPFTable.XWPFBorderType.SINGLE).withSize(8 * 3).withColor("6062e2")
                .build();
    }

    private BorderStyle attributeBorderStyle() {
        return BorderStyle.builder().withType(XWPFTable.XWPFBorderType.SINGLE).withSize(8 * 2).withColor("b26b69")
                .build();
    }

    private void addBlankLine(DocumentRenderData document) {
        document.addParagraph(Paragraphs.of("").create());
    }
}
